using System;
using System.Reflection;

namespace Wra4Skype.EmbeddedAssemblies
{
    public static class EmbedAssembly
    {
        // this class is static, and should not be instantiated.

        // Init is the only public callable method of this class.  It should be called once
        // (and only once!)
        // to register the LoadComponentAssembly method as the event handler to call should the 
        // AssemblyResolve event get fired.
        public static void Init()
        {
            AppDomain.CurrentDomain.AssemblyResolve += LoadComponentAssembly;
        }

        // This method is called when an assembly fails to load via probing path, GAC, etc.
        // Note that we return null to the CLR if we have no assembly to load. This is 
        // expected behavior, so that the CLR may notify the rest of the application that
        // it could not load the assembly in question.
        private static Assembly LoadComponentAssembly(object sender, ResolveEventArgs args)
        {
            // We'll use this reference fairly often in the future...
            var assembly = Assembly.GetExecutingAssembly();

            // Get the requested assembly's simple name (no namespace info or file extension)
            var simpleName = args.Name;
            var commaIndex = simpleName.IndexOf(',');
            if (commaIndex != -1)
            {
                simpleName = simpleName.Substring(0, commaIndex);
            }

            var dllImageResourceName = GetResourceLibName(simpleName, assembly);

            return StreamFromResource(dllImageResourceName, assembly);
        }

        // We will go through the list of resources in the assembly and using the 
        // simpleLibName, we will find if the dll resource is embedded in the assembly
        // Note that we return null on purpose if we didn't find anything.
        // This is because we also want to return null to the CLR if we have no assembly to load.
        private static string GetResourceLibName(string simpleLibName, Assembly assembly)
        {
            if (simpleLibName == null || assembly == null)
            {
                return null;
            }

            simpleLibName += ".dll";    // assume that the file ends in this extension.

            string dllImageResourceName = null;

            // We will iterate through the list of resources in this assembly,
            // looking for the name of the assembly that failed to load from disk
            foreach (var resourceName in assembly.GetManifestResourceNames())
            {
                if (resourceName.Length < simpleLibName.Length)
                {
                    continue;
                }

                // if the simpleName and resourceName end the same (we drop namespace info here),
                // then this should be the embedded assembly that we are looking for.
                if (string.Compare(simpleLibName, 0, resourceName, (resourceName.Length - simpleLibName.Length), simpleLibName.Length, true) == 0)
                {
                    dllImageResourceName = resourceName;
                }
            }

            return dllImageResourceName;
        }

        // this is the 'workhorse' of the class.  Once we've got a resource name in the assembly,
        // we stream the resource to a byte array, and load the Assembly from the byte array.
        private static Assembly StreamFromResource(string dllImageResourceName, Assembly assembly)
        {
            if (dllImageResourceName == null | assembly == null)
            {
                return null;
            }

            var imageStream = assembly.GetManifestResourceStream(dllImageResourceName);
            if (imageStream == null)
            {
                return null;
            }

            var bytestreamMaxLength = (int)imageStream.Length;

            var buffer = new byte[bytestreamMaxLength + 1];

            imageStream.Read(buffer, 0, bytestreamMaxLength);

            return Assembly.Load(buffer);
        }
    }
}